package com.ece.demo;

import static org.lwjgl.glfw.GLFW.GLFW_MOUSE_BUTTON_1;
import static org.lwjgl.glfw.GLFW.GLFW_PRESS;

import java.io.File;
import java.io.FileInputStream;
import java.util.Map;

import com.bulletphysics.collision.dispatch.PairCachingGhostObject;
import com.bulletphysics.collision.shapes.ConvexShape;
import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.dynamics.character.KinematicCharacterController;
import com.ece.demo.init.DemoInit;
import com.ece.demo.init.Sprite3DFactoryDemo;
import com.ece.demo.init.test.TestLightSprite;
import com.ece.demo.normal.SquareSprite;

import dev.ece.core.cells.model.base.BoxModel;
import dev.ece.core.cells.world.World2D;
import dev.ece.core.cells.world.World3D;
import dev.ece.core.controller.Controller;
import dev.ece.core.controller.ControllerGroup;
import dev.ece.core.engine.Engine;
import dev.ece.core.engine.EngineAdapter;
import dev.ece.core.engine.Joystick;
import dev.ece.core.engine.LoaderFlush;
import dev.ece.core.engine.LoaderQueue;
import dev.ece.core.shader.Shader;
import dev.ece.core.texture.FBO;
import dev.ece.core.texture.Texture;
import dev.ece.suit.bloom.BloomHDRComponent;
import dev.ece.suit.defer.DecalComponent;
import dev.ece.suit.defer.DeferRenderComponent;
import dev.ece.suit.defer.DeferRenderShader;
import dev.ece.suit.heightmap.HeightMapSprite;
import dev.ece.suit.loader.BoneModel3D;
import dev.ece.suit.loader.FrameModel3D;
import dev.ece.suit.loader.Sprite3D;
import dev.ece.suit.loader.models.dae.DAEModelLoader;
import dev.ece.suit.loader.models.fbx.FBXModelLoader;
import dev.ece.suit.loader.models.obj.ObjModelLoader;
import dev.ece.suit.multisample.MultiSampleComponent;
import dev.ece.suit.skybox.SkyBox;
import dev.ece.suit.text.RangeColorText;
import dev.ece.suit.text.TextLibrary;
import dev.ece.util.GLUtils;
import dev.ece.util.jbullet.EceJbulletWorld;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.lib.Vector4f;
import dev.ece.util.tools.Constants;
import dev.ece.util.tools.Screen;
import dev.ece.util.tools.ploy.TextureEdgePloy;
import dev.ece.util.tools.transformations.Rotation;
import dev.ece.util.tools.transformations.Scale;
import dev.ece.util.tools.transformations.Translation;

/**
 * @author 潜行的青衣
 * @see
 */
public class GameStarter extends EngineAdapter {
	
	private World3D world;
	
	private World2D desktop;
	
	private Screen screen;

	private DeferRenderComponent deferRenderComponent;
	
	private BloomHDRComponent bloomhdr;
	
	private MultiSampleComponent multiSampleComponent;

	private Map<String, String> functions;
	
	private ControllerGroup controllers = new ControllerGroup();
	
	private TextLibrary textLibrary;
	
	private EceJbulletWorld jbulletWorld = new EceJbulletWorld();
	
	public GameStarter() {
		screen = new Screen(0, 0, 1024, 768);
		jbulletWorld.getWorld().setGravity(new javax.vecmath.Vector3f(0, -10, 0));
	}
	
	public GameStarter(int width, int height) {
		screen = new Screen(0, 0, width, height);
		jbulletWorld.getWorld().setGravity(new javax.vecmath.Vector3f(0, -10, 0));
	}
	
	@Override
	public void onCreate(Engine engine) {
		DemoInit.RUNNING = true;
		//创建灯光
		DemoInit.createLightsAndFog();
		//创建阴影渲染世界
		world = DemoInit.initWorld(screen);
		//创建桌面
		desktop = new World2D(screen);
		try {
			//延迟渲染
			this.deferRenderComponent = new DeferRenderComponent(DeferRenderShader.getDefaultDeferRenderShader(), world);
			//几倍抗锯齿fbo
			this.multiSampleComponent = DemoInit.initMultiSample(screen);
			this.bloomhdr = DemoInit.initHDR(desktop, screen);
			//初始化字库
			this.textLibrary = DemoInit.initTextLibrary();
			
			functions = DemoInit.initFunctionsAndShader();
			
			//模型异步加载
			LoaderQueue.push((LoaderFlush flush) -> {
				
				System.out.println("开始加载地图");
				HeightMapSprite heightMapSprite = DemoInit.initHeightMap(world, functions);
				System.out.println("加载地图完成");
				
				System.out.println("开始加载天空盒");
				float width = heightMapSprite.getModel().getScale() * Math.max(heightMapSprite.getModel().getCols(), heightMapSprite.getModel().getRows() * 1.2f);
				Texture cube = Texture.newCubeInstance(new String[] {
					Constants.getGameFileDir("/assets/images/skybox/right.jpg"),
					Constants.getGameFileDir("/assets/images/skybox/left.jpg"),
					Constants.getGameFileDir("/assets/images/skybox/top.jpg"),
					Constants.getGameFileDir("/assets/images/skybox/bottom.jpg"),
					Constants.getGameFileDir("/assets/images/skybox/back.jpg"),
					Constants.getGameFileDir("/assets/images/skybox/front.jpg")
				});
				SkyBox sky = new SkyBox(width, width, cube);
				sky.getPosition().setX(heightMapSprite.getModel().getScale() * heightMapSprite.getModel().getCols() / 2.0f);
				sky.getPosition().setZ(heightMapSprite.getModel().getScale() * heightMapSprite.getModel().getRows() / 2.0f);
				System.out.println("加载天空盒完成");
				System.out.println("开始加载精灵");
				Sprite3D<Float> sprite = DemoInit.initSprite(world, functions);
				sprite.setMass(1f);
				Vector3f position = new Vector3f();
				position.set(world.getCamera().position);
				position.setY(heightMapSprite.getModel().getHeight(position.getX(), position.getZ()));
				System.out.println("加载精灵完成");
				
				System.out.println("加载测试box开始");
				BoxModel box = new BoxModel(1, 1, 1);
				TestLightSprite test = new TestLightSprite(box);
				test.getMaterial().setAlpha(0.4f);
				test.setAlpha(true);
				System.out.println(test.isAlphaRender());
				System.out.println("加载测试box结束");
				
				System.out.println("加载DAE模型开始");
				DAEModelLoader daeloader = new DAEModelLoader(Constants.getGameFileInputStream("/assets/models/冰狼.DAE"));
				Sprite3D<Float> dae = new Sprite3D<Float>(new BoneModel3D(daeloader));
				Vector3f daeposition = new Vector3f();
				daeposition.set(position);
				daeposition.setX(daeposition.getX() - 2);
				daeposition.setZ(daeposition.getZ() - 3);
				daeposition.setY(heightMapSprite.getModel().getHeight(daeposition.getX(), daeposition.getZ()));
				dae.action("all");
				System.out.println("加载DAE模型结束");
				
				System.out.println("加载FBX模型开始");
				FBXModelLoader fbxloader = new FBXModelLoader(Constants.getGameFileInputStream("/assets/models/naerQ.FBX"));
				Sprite3D<Float> fbx = new Sprite3D<Float>(new BoneModel3D(fbxloader));
				
				float scale = 0.01f;
				Vector3f fbxposition = new Vector3f();
				fbxposition.set(position);
				fbxposition.setX(fbxposition.getX() - 3);
				fbxposition.setZ(fbxposition.getZ() - 3);
				fbxposition.setY(heightMapSprite.getModel().getHeight(fbxposition.getX(), fbxposition.getZ()));
				fbx.transformations.add(new Translation(fbxposition));
				fbx.transformations.add(new Scale(new Vector3f(scale, scale, scale)));
				fbx.action("all");
				System.out.println("加载FBX模型结束");
				//添加个延迟贴花
				String path = "/assets/images/face.png";
				Texture texture = Texture.newInstance(Constants.getGameFileDir(path), TextureEdgePloy.getInstance());
				DecalComponent decal = new DecalComponent(texture, 0.5f);
				Vector3f pos = new Vector3f(daeposition.getX(), 0, daeposition.getZ());
				pos.setY(heightMapSprite.getModel().getHeight(pos.getX(), pos.getZ()));
				decal.transformations.add(new Translation(pos));
				decal.transformations.add(new Rotation(new Vector4f(-1, 0, 0, 90)));
				//加载完成
				flush.finish();
				
				//地图添加到物理引擎中
				RigidBody ground = heightMapSprite.addToEceJbulletWorld(jbulletWorld);
				ground.setFriction(1);
				ground.setRestitution(1);
				
				//把测试方块放到物理引擎中
				test.setMass(1);
				Vector3f testPosition = new Vector3f();
				testPosition.set(position);
				testPosition.setY(4);
				testPosition.setZ(position.getZ() - 2);
				test.addToEceJbulletWorld(jbulletWorld, testPosition);
				
				
				//人物添加到物理引擎
				position.setZ(position.getZ() - 2);
				position.setY(position.getY() + 3);
				
				PairCachingGhostObject obj = sprite.addGhostObjectToEceJbulletWorld(
						jbulletWorld, position, new Vector4f(0, 1, 0, 0), new Vector3f(0.1f, 0.1f, 0.1f));
				obj.setFriction(1);
				obj.setRestitution(1);
				
				KinematicCharacterController con = new KinematicCharacterController(obj, (ConvexShape) obj.getCollisionShape(), 0.5f);
				jbulletWorld.addKinematicCharacterController(con);
				
				//添加控制
				Controller controller = DemoInit.initController(this.desktop, world, this.bloomhdr, heightMapSprite.getModel(), sprite, con);
				controllers.add(controller);
				
				dae.addToEceJbulletWorld(jbulletWorld, daeposition, null, new Vector3f(0.01f, 0.01f, 0.01f));
				//
				world.add(sky);
				world.add(heightMapSprite);
				world.add(sprite);
				world.add(decal);
				world.add(test);
				world.add(dae);
				world.add(fbx);
			});
			
			LoaderQueue.push((LoaderFlush flush) -> {
				System.out.println("加载MS3D模型开始");
				Sprite3D<Float> sprite = Sprite3DFactoryDemo.getSprite3DFactory().loadBoneSprite3D("ninja.ms3d");
				sprite.setMass(1);
				sprite.action("all");
				
				System.out.println("加载MS3D模型结束");
				
				System.out.println("加载OBJ模型开始");
				File mtl = Constants.getGameFile("/assets/models/黄衣少女.mtl");
				File file = Constants.getGameFile("/assets/models/黄衣少女.obj");
				
				ObjModelLoader objloader = new ObjModelLoader(new FileInputStream(mtl), new FileInputStream(file));
				FrameModel3D fmodel = new FrameModel3D(objloader);
				Sprite3D<Integer> obj = new Sprite3D<Integer>(fmodel);
				obj.setMass(1);
				System.out.println("加载OBJ模型结束");
				
				flush.finish();
				
				world.add(sprite);
				world.add(obj);
				
				Vector3f position = new Vector3f();
				position.set(world.getCamera().position);
				position.setY(0.039215688f);
				
				((RigidBody) sprite.addToEceJbulletWorld(jbulletWorld, position, null, new Vector3f(0.1f, 0.1f, 0.1f))).setAngularFactor(0);
				
				position.setY(3);
				((RigidBody) obj.addToEceJbulletWorld(jbulletWorld, position, null, new Vector3f(0.5f, 0.5f, 0.5f))).setAngularFactor(0);
			});
			
			//异步加载
			LoaderQueue.push((LoaderFlush flush) -> {
				System.out.println("开始加载法线贴图");
				SquareSprite sprite = DemoInit.initNormalMapping(world, functions);
				System.out.println("加载法线贴图完毕");
				flush.finish();
				
				world.add(sprite);
			});
			
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void onScreenChange(Engine engine, Screen screen, int width, int height) throws Exception {
		multiSampleComponent.setScreen(screen);
	}

	@Override
	public void onJoyStickEvent(Joystick joystick, int button, int event) throws Exception {
		System.out.println(String.format("jid:%s,button:%s,event:%s", joystick.getJid(), button, event));
	}

	@Override
	public void onJoyStickAxesEvent(Joystick joystick, int axe, float value) throws Exception {
		System.out.println(String.format("jid:%s,axe:%s,event:%s", joystick.getJid(), axe, value));
	}

	@Override
	public void onKeyBoardEvent(long window, int key, int scancode, int action, int mods) {
		controllers.keyBoardEvent(key, action);
	}

	@Override
	public void onMouseButtonEvent(long window, int button, int action, int mods, Pointer pointer) throws Exception {
		//窗口大小变化后，坐标变化
		Pointer newpointer = newPointer(pointer);
		//测试数据
		if(GLFW_PRESS == action) {
			if(button == GLFW_MOUSE_BUTTON_1) {
				System.out.println(pointer.getX() + "," + pointer.getY());
				Vector3f point = this.desktop.getPointer(newpointer.getX(), newpointer.getY());
				System.out.println(point);
				Vector3f[] points = this.world.getPointer(newpointer.getX(), newpointer.getY());
				System.out.println(points[0].toString());
				System.out.println(points[1].toString());
			}
		}
		if(this.desktop.mouseButtonEvent(window, button, action, mods, newpointer)) {
			return;
		}
		this.world.mouseButtonEvent(window, button, action, mods, newpointer);
	}

	@Override
	public void onMouseMoveEvent(long window, Pointer pointer) throws Exception {
		Pointer newpointer = newPointer(pointer);
		if(this.desktop.mouseMoveEvent(window, newpointer)) {
			return;
		}
		this.world.mouseMoveEvent(window, newpointer);
	}

	@Override
	public void onMouseScrollEvent(long window, double xoffset, double yoffset, Pointer pointer) throws Exception {
		Pointer newpointer = newPointer(pointer);
		if(this.desktop.mouseScrollEvent(window, xoffset, yoffset, newpointer)) {
			return;
		}
		this.world.mouseScrollEvent(window, xoffset, yoffset, newpointer);
	}
	
	/**
	 * 窗口变化后鼠标位置需要坐标转换
	 * @param pointer
	 * @return
	 */
	public Pointer newPointer(Pointer pointer) {
		return new Pointer(
				pointer.getX() * screen.getWidth() / this.multiSampleComponent.getScreen().getWidth(), 
				pointer.getY() * screen.getHeight() / this.multiSampleComponent.getScreen().getHeight());
	}
	
	@Override
	public void onLogic(Engine engine) throws Exception {
		//控制器运算
		controllers.execute();
		//物理引擎运算
		jbulletWorld.stepSimulation(Engine.currentTimer().getDalyTime(), 10);
		//逻辑运算
		world.logic();
		desktop.logic();
	}

	@Override
	public void onRender(Engine engine) {
		//初始化
		world.initialize();
		desktop.initialize();
		multiSampleComponent.initialize();
		
		//绘制深度图（shadow mapping需要）
		world.renderDepthMap();
		//延迟渲染
		deferRenderComponent.begin();
		//渲染世界+贴花纹理
		world.render(deferRenderComponent.getDefer());
		
		//开始bloomhdr
		bloomhdr.begin();
		//渲染灯光、阴影
		deferRenderComponent.render();
		
		//高斯模糊
		bloomhdr.renderGaussianBlur();
		
		//抗锯齿FBO 开启
		multiSampleComponent.begin();
		GLUtils.glEnableAlpha();
		desktop.render();
		Vector3f position = world.getCamera().position;
		//使用desktop的投影矩阵写字
		String text = String.format("摄像头坐标:[color=#ff0000]x:%f,y:%f,z:%f[/]", position.getX(), position.getY(), position.getZ());
		String[] lines = new String[] {
			"FBX模型真是神烦，还是没能搞定，搁置中",
			"按键1:灯光0开关", 
			"按键2:灯光1开关", 
			"按键3:HDR开关",
			"按键4:UI展示开关",
			text,
			"你再看,再看就把你吃掉!ABCDEFGHIJ[color=#ff0000]KLMNO[/]PQRSTUVWXYZab[color=#0000ff]cdefghijk[/]lmnopqrstuvwxyz",
			"1234567890"
		};
		RangeColorText colorText = textLibrary.createTangeColorText(lines);
		colorText.getDefaultColor().set(0, 1, 1, 1);
		textLibrary.renderColorText(colorText, 24, new Vector3f(-450, textLibrary.getLineHeight(24) * 3, 0));
//		textLibrary.renderSimpleText(new String[] {"FBX模型真是神烦，还是没能搞定，搁置中"}, 24, new Vector4f(1f, 0, 0, 1f), new Vector3f(-450, -textLibrary.getLineHeight(24), 0));
		GLUtils.glDisableAlpha();
		//FBO 绘制结束
		FBO.finish();
		//渲染抗锯齿屏幕
		multiSampleComponent.render();
	}
	
	@Override
	public void onEngineDestroy() {
		DemoInit.RUNNING = false;
		try {
			jbulletWorld.destroy();
		} catch(Exception e) {}
		try {
			this.deferRenderComponent.destroy();
		} catch(Exception e) {}
		try {
			this.desktop.destroy();
		} catch(Exception e) {}
		try {
			this.world.destroy();
		} catch(Exception e) {}
		try {
			multiSampleComponent.destroy();
		} catch(Exception e) {}
		try {
			textLibrary.destroy();
		} catch(Exception e) {}
		try {
			Shader.destroyAllShader();
		} catch(Exception e) {}
	}

	public void start() throws Exception {
		Engine engine = new Engine(this, this);
		engine.start(screen.getWidth(), screen.getHeight(), true);
	}

	public static void main(String[] args) throws Exception {
		GameStarter gameStarter = new GameStarter();
		gameStarter.start();
	}
	
}
